import logging

from apiflask import APIBlueprint
from apiflask import abort
from flask.views import MethodView

from eAuth.base.schemas import BaseOutSchema
from eAuth.extensions import db
from eAuth.models import Api, Role, roles_apis
from eAuth.utils.decorator import operate_log, refresh_cache
from eAuth.utils.model import get_page, get_api_query, get_role_query
from .schema import ApiQuerySchema, ApiPageOutputSchema, ApiInputSchema, ApiSingleOutputSchema, \
    ApiConditionsOutputSchema
from ..role.schema import RolePageOutputSchema, RoleQuerySchema, RoleIdListInputSchema

config_api = APIBlueprint("config_api", __name__, url_prefix="/api")

logger = logging.getLogger(__name__)


class ApiView(MethodView):
    @config_api.input(ApiQuerySchema, location="query", arg_name="query")
    @config_api.output(ApiPageOutputSchema)
    @config_api.doc(summary="获取API",
                    responses=[200, 401, 403, 500],
                    security="Authorization")
    def get(self, api_id: int, query: dict):
        api_query = get_api_query(query)
        return get_page(api_query, {"id": api_id}, query["page"], query["per_page"])

    @operate_log
    @refresh_cache
    @config_api.input(ApiInputSchema, location="json", arg_name="data")
    @config_api.output(ApiSingleOutputSchema, status_code=201)
    @config_api.doc(summary="创建API",
                    responses=[201, 401, 403, 422, 500],
                    security="Authorization")
    def post(self, data: dict):
        api = Api(**data)
        try:
            db.session.add(api)
            db.session.commit()
        except:
            logger.error("[api] Create failed", exc_info=True)
            db.session.rollback()
            abort(500, message="server error")
        return {"data": api}

    @operate_log
    @refresh_cache
    @config_api.input(ApiInputSchema, location="json", arg_name="data")
    @config_api.output(ApiSingleOutputSchema, status_code=201)
    @config_api.doc(summary="修改API",
                    responses=[201, 401, 403, 404, 422, 500],
                    security="Authorization")
    def put(self, api_id: int, data: dict):
        api = Api.query.get_or_404(api_id)
        for field, value in data.items():
            setattr(api, field, value)
        try:
            db.session.commit()
        except:
            logger.error("[api] Update failed", exc_info=True)
            db.session.rollback()
            abort(500, message="server error")
        return {"data": api}

    @operate_log
    @refresh_cache
    @config_api.output(BaseOutSchema)
    @config_api.doc(summary="删除API",
                    responses=[200, 401, 403, 404, 500],
                    security="Authorization")
    def delete(self, api_id: int):
        api = Api.query.get_or_404(api_id)
        try:
            db.session.delete(api)
            db.session.commit()
        except:
            logger.error("[api] Delete failed", exc_info=True)
            db.session.rollback()
            abort(500, message="server error")
        return {"success": True}


api_view = ApiView.as_view("api_view")
config_api.add_url_rule("", view_func=api_view, defaults={"api_id": None}, methods=["GET"])
config_api.add_url_rule("", view_func=api_view, methods=["POST"])
config_api.add_url_rule("/<int:api_id>", view_func=api_view, methods=["GET", "PUT", "DELETE"])


@config_api.put("/<int:api_id>/role")
@operate_log
@refresh_cache
@config_api.input(RoleIdListInputSchema, location="json", arg_name="data")
@config_api.output(ApiSingleOutputSchema, status_code=201)
@config_api.doc(summary="传入角色列表，为api绑定角色",
                responses=[201, 401, 403, 404, 500],
                security="Authorization")
def api_add_roles(api_id: int, data: dict):
    # 查询API
    api: Api = Api.query.get_or_404(api_id)
    # 筛选出需要添加的Role id列表
    ids = set(data["ids"])
    current_ids = set(role.id for role in api.roles)
    add_ids = list()
    for id_ in ids:
        if id_ not in current_ids:
            add_ids.append(id_)
    logger.info(f"[api-add-roles] Api `{api.method} {api.url}` will add roles {add_ids}")
    try:
        # 为角色添加API
        role_add_list = Role.query.filter(Role.id.in_(add_ids)).all()
        api.roles.extend(role_add_list)
        db.session.commit()
    except:
        logger.error("[api-add-roles] Update failed", exc_info=True)
        db.session.rollback()
        abort(500, message="server error")
    return {
        "data": api
    }


@config_api.delete("/<int:api_id>/role")
@operate_log
@refresh_cache
@config_api.input(RoleIdListInputSchema, location="json", arg_name="data")
@config_api.output(ApiSingleOutputSchema, status_code=201)
@config_api.doc(summary="传入角色列表，为api解绑角色",
                responses=[201, 401, 403, 404, 500],
                security="Authorization")
def api_remove_roles(api_id: int, data: dict):
    # 查询API
    api: Api = Api.query.get_or_404(api_id)
    # 筛选出需要删除的角色id列表
    ids = set(data["ids"])
    current_roles = api.roles.copy()
    for role in api.roles:
        if role.id in ids:
            current_roles.remove(role)
    logger.info(f"[api-remove-roles] Api `{api.method} {api.url}` will set roles={[role.id for role in current_roles]}")
    try:
        api.roles = current_roles
        db.session.commit()
    except:
        logger.error("[api-remove-roles] Delete failed", exc_info=True)
        db.session.rollback()
        abort(500, message="server error")
    return {
        "data": api
    }


@config_api.get("/conditions")
@config_api.output(ApiConditionsOutputSchema)
@config_api.doc(summary="获取API查询参数",
                responses=[200, 401, 403, 500],
                security="Authorization")
def getApiConditions():
    result = [
        item[0]
        for item in
        db.session.query(Api.service).order_by(Api.service.asc()).distinct().all()
        if item[0]
    ]
    return {
        "data": {
            "services": result
        }
    }


@config_api.get('/<int:api_id>/bound-roles')
@config_api.input(RoleQuerySchema, location="query", arg_name="query")
@config_api.output(RolePageOutputSchema)
@config_api.doc(summary="获取API对应的角色列表",
                responses=[200, 401, 403, 500],
                security="Authorization")
def queryApiBoundRoles(api_id: int, query: dict):
    role_query = get_role_query(query)
    # 联表查询
    return get_page(
        role_query.join(roles_apis).join(Api).filter(Api.id == api_id).order_by(Role.id.asc()),
        {},
        query["page"],
        query["per_page"],
        api_id=api_id
    )


@config_api.get('/<int:api_id>/unbound-roles')
@config_api.input(RoleQuerySchema, location="query", arg_name="query")
@config_api.output(RolePageOutputSchema)
@config_api.doc(summary="获取API未绑定的角色列表",
                responses=[200, 401, 403, 500],
                security="Authorization")
def queryApiUnboundRoles(api_id: int, query: dict):
    role_query = get_role_query(query)
    # 子查询，先查询该api已绑定的role的id列表，再查询不在这个列表内的所有role
    sub_query = db.session.query(Role.id).join(roles_apis).join(Api).filter(Api.id == api_id).subquery()
    # 联表查询
    return get_page(
        role_query.filter(Role.id.notin_(sub_query)).order_by(Role.id.asc()),
        {},
        query["page"],
        query["per_page"],
        api_id=api_id
    )
